/*
 * Copyright (c) Huawei Technologies Co., Ltd. 2015-2018. All rights reserved.
 * Description: Signature Verify CBB Library
 * Author: tangzonglei
 * Create: 2015
 * History: 2018/11/23 yebin code rule fixes
 */
#include "../cmscbb_common/cmscbb_base64.h"
#include "../cmscbb_common/cmscbb_common.h"
#include "../cmscbb_common/cmscbb_def.h"

#if CMSCBB_SUPPORT_PEM
#define BASE64_MAP_SIZE 128
#define BASE64_UNIT_LEN 4
/* map of radix64 and byte */
static const CVB_BYTE G_CVB_BASE64_DEC_MAP[BASE64_MAP_SIZE] = {
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127, 127, 127, 127, 127, 127, 127, 127,
    127, 127, 127,  62, 127, 127, 127,  63,  52,  53,
    54,   55,  56,  57,  58,  59,  60,  61, 127, 127,
    127,  64, 127, 127, 127,   0,   1,   2,   3,   4,
    5,     6,   7,   8,   9,  10,  11,  12,  13,  14,
    15,   16,  17,  18,  19,  20,  21,  22,  23,  24,
    25,  127, 127, 127, 127, 127, 127,  26,  27,  28,
    29,   30,  31,  32,  33,  34,  35,  36,  37,  38,
    39,   40,  41,  42,  43,  44,  45,  46,  47,  48,
    49,   50,  51, 127, 127, 127, 127, 127
};

#define MAX_ASCII_VALUE 127
#define PAIR_LENGTH_OF_BYTE 3
#define CRCL_SIZE 2 /* /r&/n */
#define CL_SIZE 1 /* /n Only */
#define ASCII_BITS_COUNT 6
#define MASK_CODE_FIRST_IN_SECOND 0x30
#define MASK_CODE_SECOND_IN_SECOND 0x0F
#define MASK_CODE_SECOND_IN_THIRD 0x3C
#define MASK_CODE_THIRD_IN_THIRD 0x03
#define MASK_CODE_ALL_ONE 0xFF
#define ILLEGAL_ELEMENT 0x7F
#define MIN_BASE64_STR_LEN 4
#define MOVE_TWO_BITS 2
#define MOVE_FOUR_BITS 4
#define MOVE_SIX_BITS 6

CVB_STATIC CVB_BOOL IsSpace(CVB_BYTE ch)
{
    CVB_BOOL flag = (ch == ' ') || (ch == '\t') || (ch == '\n') || (ch == '\v') || (ch == '\f') || (ch == '\r');
    if (flag) {
        return CVB_TRUE;
    }
    return CVB_FALSE;
}

CVB_STATIC CVB_UINT32 RightTrimLen(const CVB_CHAR* str, CVB_UINT32 strlen)
{
    CVB_UINT32 endIndex = strlen;
    while (endIndex > 0 && IsSpace((CVB_BYTE)str[endIndex - 1])) {
        endIndex--;
    }
    return endIndex;
}

/*
 * Prototype    : InternalIsEndOfLine
 * Description  : check if read point reach the end of line.
 * Params
 *   [IN] encoded: encode CHAR list
 *   [IN] encodedLen: the length of encode
 *   [IN] i: position
 * Return Value : CVB_BOOL
 *   Date              Author     Modification
 *   2015/11/10 19:31  tangzonglei  Create
 */
CVB_STATIC CVB_BOOL InternalIsEndOfLine(const CVB_CHAR* encoded, CVB_UINT32 encodedLen, CVB_INT i)
{
    if (((CVB_INT)encodedLen - i) >= CRCL_SIZE && encoded[i] == '\r' && encoded[i + 1] == '\n') {
        return CVB_TRUE;
    }

    if (((CVB_INT)encodedLen - i) >= CL_SIZE && encoded[i] == '\n') {
        return CVB_TRUE;
    }

    return CVB_FALSE;
}

/*
 * Prototype    : InternalB64GetDecLen
 * Description  : calculate the length after base64 decoded.
 * Params
 *   [IN] encoded: base64 encode char list
 *   [IN] encodedLen: length of encode
 *   [IN] valid: valid length
 *   [IN] len: output length
 * Return Value : CMSCBB_ERROR_CODE
 *   Date              Author     Modification
 *   2015/11/10 19:33  tangzonglei  Create
 */
CVB_STATIC CMSCBB_ERROR_CODE InternalB64GetDecLen(const CVB_CHAR* encoded, CVB_UINT32 encodedLen, CVB_INT* valid,
    CVB_INT *len)
{
    CVB_INT i = 0;
    CVB_INT padNum = 0;
    CVB_INT realLen = 0;

    CVB_INT newLen = (CVB_INT)RightTrimLen(encoded, encodedLen);
    while (i < newLen) {
        /* allow LF in input base64 string */
        if (InternalIsEndOfLine(encoded, encodedLen, i) == CVB_TRUE) {
            i++;
            continue;
        }
        if (encoded[i] == ' ') {
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }

        if ((CVB_BYTE)encoded[i] > MAX_ASCII_VALUE) {
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }

        /* Only at most 2 '=' in base64 string at the end of the string */
        if (encoded[i] == '=' && i < (CVB_INT)(newLen - 2)) { /* 2 is num of str at the end of the str base64 */
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }

        if (encoded[i] == '=') {
            padNum++;
        }
        i++;
        realLen++;
    }

    if (realLen % BASE64_UNIT_LEN != 0) {
        CVB_LOG_ERROR(CMSCBB_ERR_SYS_UTIL_B64_DEC, CVB_NULL);
        return CMSCBB_ERR_SYS_UTIL_B64_DEC;
    }
    *valid = i;
    /* each base64 byte has 6 useful bits, finally it would convert to 8 bit after decode,
     * so the formular is like (len * 6 + 7) / 8; add 7 because the result expected ceiling valued
     * */
    realLen = (CVB_INT)(((CVB_UINT)((realLen * ASCII_BITS_COUNT) + (ASCII_BITS_COUNT + 1))) >> PAIR_LENGTH_OF_BYTE);
    realLen -= (CVB_INT)padNum;
    *len = realLen;
    return CVB_SUCCESS;
}

CVB_STATIC CMSCBB_ERROR_CODE ValidateInputStr(const CVB_CHAR* encodeStr, CVB_INT encodeLen)
{
    if ((encodeStr == NULL) || (encodeLen < MIN_BASE64_STR_LEN)) {
        return CMSCBB_ERR_SYS_UTIL_B64_DEC;
    }
    return CVB_SUCCESS;
}

CVB_STATIC CMSCBB_ERROR_CODE ValidateInputChar(CVB_BYTE inputChar)
{
    if ((inputChar > MAX_ASCII_VALUE) || (G_CVB_BASE64_DEC_MAP[inputChar & MASK_CODE_ALL_ONE] == ILLEGAL_ELEMENT)) {
        return CMSCBB_ERR_SYS_UTIL_B64_DEC;
    }
    return CVB_SUCCESS;
}

CVB_STATIC CMSCBB_ERROR_CODE GetNextChar(const CVB_CHAR* encodeStr, CVB_INT encodeLen,
    CVB_INT *index, CVB_BYTE *nextChar)
{
    CVB_BYTE curChar;

    if (*index > encodeLen) {
        return CMSCBB_ERR_SYS_UTIL_B64_DEC;
    }
    curChar = (CVB_BYTE)encodeStr[(*index)++];

    while (curChar == '\r' || curChar == '\n') {
        if (*index > encodeLen) {
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }
        curChar = (CVB_BYTE)encodeStr[(*index)++];
    }
    if (*index > encodeLen) {
        return CMSCBB_ERR_SYS_UTIL_B64_DEC;
    }
    *nextChar = curChar;
    return ValidateInputChar(curChar);
}

/*
 * Prototype    : InternalB64DoDec
 * Description  : decode base64
 * Params
 *   [IN] encoded: encode by base64
 *   [IN] encodeLen: length of encode by base64
 *   [IN] decoded: decode from base64
 *   [out] decodeLen : length of decode of by base64
 * Return Value : CVB_UINT32
 *   Date              Author     Modification
 *   2015/11/10 19:46  tangzonglei  Create
 */
CVB_STATIC CMSCBB_ERROR_CODE InternalB64DoDec(const CVB_CHAR* encodeStr, CVB_INT encodeLen, CVB_BYTE* decodeStr,
    CVB_UINT32* decodeLen)
{
    CVB_INT inIdx = 0;
    CVB_UINT32 outIdx = 0;
    CVB_BYTE c1, c2, c3, c4;

    if (ValidateInputStr(encodeStr, encodeLen) != CVB_SUCCESS) {
        return CMSCBB_ERR_SYS_UTIL_B64_DEC;
    }

    while (inIdx < encodeLen) {
        if (GetNextChar(encodeStr, encodeLen, &inIdx, &c1) != CVB_SUCCESS) {
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }
        c1 = (CVB_BYTE)G_CVB_BASE64_DEC_MAP[(CVB_INT)c1];

        if (GetNextChar(encodeStr, encodeLen, &inIdx, &c2) != CVB_SUCCESS) {
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }
        c2 = (CVB_BYTE)G_CVB_BASE64_DEC_MAP[(CVB_INT)c2];
        decodeStr[outIdx++] = (CVB_BYTE)((c1 << MOVE_TWO_BITS) | ((c2 & MASK_CODE_FIRST_IN_SECOND) >> MOVE_FOUR_BITS));

        if (GetNextChar(encodeStr, encodeLen, &inIdx, &c3) != CVB_SUCCESS) {
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }
        if (c3 == '=') {
            break;
        }
        c3 = (CVB_BYTE)G_CVB_BASE64_DEC_MAP[(CVB_INT)c3];
        decodeStr[outIdx++] = (CVB_BYTE)(((c2 & MASK_CODE_SECOND_IN_SECOND) << MOVE_FOUR_BITS) |
            ((c3 & MASK_CODE_SECOND_IN_THIRD) >> MOVE_TWO_BITS));

        if (GetNextChar(encodeStr, encodeLen, &inIdx, &c4) != CVB_SUCCESS) {
            return CMSCBB_ERR_SYS_UTIL_B64_DEC;
        }
        if (c4 == '=') {
            break;
        }
        c4 = (CVB_BYTE)G_CVB_BASE64_DEC_MAP[(CVB_INT)c4];
        decodeStr[outIdx++] = (CVB_BYTE)(((c3 & MASK_CODE_THIRD_IN_THIRD) << MOVE_SIX_BITS) | c4);
    }
    *decodeLen = outIdx;
    return CVB_SUCCESS;
}

CMSCBB_ERROR_CODE CmscbbBase64Decode(const CVB_CHAR* encoded, CVB_UINT32 encodedLen, CVB_BYTE* decoded,
    CVB_UINT32* decodedLen)
{
    CMSCBB_ERROR_CODE ret;
    CVB_INT dec = 0;
    CVB_INT valid = 0;

    if ((encoded == CVB_NULL) || (encodedLen == 0) || (decodedLen == CVB_NULL)) {
        return CMSCBB_ERR_CONTEXT_INVALID_PARAM;
    }

    ret = InternalB64GetDecLen(encoded, encodedLen, &valid, &dec);
    if (CVB_FAILED(ret)) {
        return ret;
    }

    if (decoded == CVB_NULL || (CVB_INT)*decodedLen < dec) {
        /* only require output length */
        *decodedLen = (CVB_UINT32)dec;
        return CVB_SUCCESS;
    }

    return InternalB64DoDec(encoded, valid, decoded, decodedLen);
}
#endif

